/**
 * Created by AMD on 06.11.14.
 */
package model {
import controller.DebugErrorCommand;

import flash.events.ErrorEvent;
import flash.geom.Point;

import flash.geom.Point;

import messages.DebugMessage;
import messages.ModelToControllerMessage;

import messages.ModelToViewMessage;

import model.objects.CellData;
import model.objects.ShopDesignData;

import model.objects.ShopObjectData;
import model.objects.ShopperData;
import model.WarehouseDataProxy;
import model.prototypes.ShopObjectProto;

import mvcexpress.mvc.Proxy;



//-----------------------------------------------------------------------------------------------
// В этом прокси будет храниться информация о всех параметрах,
// необходимых для работы приложения в реальном времени
//-----------------------------------------------------------------------------------------------


public class RuntimeProxy extends Proxy{
    [Inject]
    public var userProxy:UserProxy;
    //-----for drag game layer
    private var _dragSnapPoint:Point;
    private var _dragGameLayer:Boolean;
    private var _dragInertion:Point = new Point();
    //---------------------------

    private var _shopDesign:ShopDesignData = null;

    private var _staticObjects:Vector.<ShopObjectData> = new Vector.<ShopObjectData>();
    private var _gameField:Object = {};

    private var _shoppersPool:Vector.<ShopperData> = new Vector.<ShopperData>();


    private var _currentTime:uint = 0;

    //mode
    private var _mode:String = Constants.MODE_SIMULATION;
    private var _oldMode:String;
    private var _modeParams:Object;

    //windows visibility
    //private var _orderProductWindowVisibility:Boolean = false;

    //locks
    private var _bottomPanelLock:Boolean = false;
    private var _wallObjectContextMenuTarget:Object = null;


    public function RuntimeProxy() {
        this._dragSnapPoint = new Point();
        this._dragGameLayer = false;
    }

    override protected function onRegister():void {

    }

    override protected function onRemove():void {

    }

    public function setDoorState(doorCoords:Point, isOpened:Boolean):void {
        sendMessage(ModelToViewMessage.SHOP_DOOR_STATE_UPDATE, {position:doorCoords, opened:isOpened});
    }

    public function get shopDesign():ShopDesignData {
        return _shopDesign;
    }

    public function set shopDesign(value:ShopDesignData):void {
        if(this._shopDesign != null){
            this._shopDesign.updateSignal.remove(_translateShopDesignUpdate);
        }
        this._shopDesign = value;
        sendMessage(ModelToViewMessage.SHOP_DESIGN_UPDATE,{type:Constants.SHOP_DESIGN_FULL_UPDATE, param: this._shopDesign});
        this._shopDesign.updateSignal.add(_translateShopDesignUpdate);
        //send message to update shop view
    }
    private function _translateShopDesignUpdate(commandName:String, param:Object):void {
          sendMessage(ModelToViewMessage.SHOP_DESIGN_UPDATE,{type:commandName, param:param});
    }

    public function get staticObjects():Vector.<ShopObjectData> {
        return this._staticObjects;
    }

    public function set staticObjects(value:Vector.<ShopObjectData>):void {
        var i:int = 0;
        for(i = 0;i < this._staticObjects.length; i++){
            removeShopObject(_staticObjects[i]);
        }
        //this._gameField = {};
        sendMessage(ModelToViewMessage.SHOP_OBJECTS_RESET);

        for(i = 0;i < value.length; i++){
            placeShopObject(value[i]);
        }
    }



    private var _bufInspectQuad:Point = new Point();
    private var _bufInspectCellData:CellData;
    public function getPlacingMatrix(quad:Point, occ_matrix:Array):Array {

        var _result:Array = [];
        var _occOffsetToCenter:int = int((occ_matrix.length-1)/2);
        var _bufMatrixSize:int = occ_matrix.length;

        //var _currentMatrix:Array = [];



        for(var i:int = 0;i< _bufMatrixSize; i++){
            _result.push([]);
            //_currentMatrix.push([]);
            for(var j:int = 0;j< _bufMatrixSize; j++){
                _result[i].push(1); // default - ok to placing (1)
                this._bufInspectQuad.y = quad.y + (i - _occOffsetToCenter);
                this._bufInspectQuad.x = quad.x + (j - _occOffsetToCenter);

                this._bufInspectCellData = this.getCellData(this._bufInspectQuad);


                //*--------------!!!ATTENTION!!!-----------------
                //*
                //*  Any changes here must be reflected in function _updateGameFieldOccPoints()
                //*
                //*--------------!!!ATTENTION!!!-----------------

                if(this._shopDesign.quadIsInside(this._bufInspectQuad.x,this._bufInspectQuad.y) == false && occ_matrix[i][j] != 0){
                    _result[i][j] = -1;//deprecate
                    continue;
                }
                if(this._shopDesign.isDoorPoint(this._bufInspectQuad) && occ_matrix[i][j] == 1){
                    _result[i][j] = -1;//deprecate
                    continue;
                }

                if (this._bufInspectCellData.occ_var == 1){//case 1://busy
                    if(int(occ_matrix[i][j]) == 1){//... under busy
                        _result[i][j] = -1;//deprecate
                    } else if(int(occ_matrix[i][j]) == 0){//... under free
                        _result[i][j] = 0;//allow 1 (because busy)
                    } else {//... under reserved
                        _result[i][j] = -1;//deprecate
                    }
                } else if(this._bufInspectCellData.occ_var == 0){//case 0://free
                    _result[i][j] = 1;//full allow
                } else {//case <0://reserved ...
                    if(int(occ_matrix[i][j]) == 1){//... under busy
                        _result[i][j] = -1;//deprecate
                    } else if(int(occ_matrix[i][j]) == 0){//... under free
                        _result[i][j] = 0;//allow 1
                    } else {//... under reserved
                        _result[i][j] = 0;//allow 1 (because reserved)
                    }
                }
            }
        }
        //trace("occ_matrix on "+quad+":"+JSON.stringify(_currentMatrix));
        return _result;
    }

    private var _bufArray:Array;
    public function canBePlaced(objectProto:ShopObjectProto, quad:Point, angle:int):Boolean {
       this._bufArray = getPlacingMatrix(quad,ShopObjectData.rotateOCCMatrix(objectProto.occ_matrix,angle));
        var _bufMatrixSize:int = this._bufArray.length;
        for(var i:int = 0;i< _bufMatrixSize; i++){
            for(var j:int = 0;j< _bufMatrixSize; j++){
                if(int(this._bufArray[i][j]) == -1){
                    return false;
                }
            }
        }
        return true;
    }

    public function placeShopObject(shopObjectData:ShopObjectData):Boolean {
        var _bufErrorMessage:String = this._updateGameFieldOccPoints (shopObjectData,true);
        if(_bufErrorMessage == null){
            this._staticObjects.push(shopObjectData);
            sendMessage(ModelToViewMessage.CREATE_SHOP_OBJECT, shopObjectData);
        } else {
            sendMessage(DebugMessage.ERROR_MESSAGE, _bufErrorMessage);
            return false;
        }
        return true;
    }

    public function removeShopObject(shopObjectData:ShopObjectData):Boolean {
        var _bufErrorMessage:String = this._updateGameFieldOccPoints (shopObjectData,false);
        if(_bufErrorMessage == null){
            var i:int = 0;
            while(i < this._staticObjects.length){
                if(this._staticObjects[i] == shopObjectData){
                    this._staticObjects.splice(i,1);
                } else {
                    i++;
                }
            }
            shopObjectData.destruct();
            //sendMessage(ModelToViewMessage.REMOVE_SHOP_OBJECT, shopObjectData);
        } else {
            sendMessage(DebugMessage.ERROR_MESSAGE, _bufErrorMessage);
            return false;
        }
        return true;
    }

    //returns error message if fail
    private function _updateGameFieldOccPoints(objectData:ShopObjectData, adding:Boolean = true):String {

        //---------ready
        //do proper occ_var on cells (with few busy points)
        //...

        //---------------!!!!ready!!!!
        //do matrix rotation according to object angle
        //...

        var occ_matrix:Array =  objectData.rotatedOCCMatrix;

        var _bufPoint:Point = new Point();
        var _objectPoint:Point = objectData.position.clone();
        var _bufCell:CellData;
        var _occOffsetToCenter:int = int((occ_matrix.length-1)/2)
        for(var i:int = 0;i< occ_matrix.length; i++){
            for(var j:int = 0;j< occ_matrix[i].length; j++){
                _bufPoint = new Point();
                _bufPoint.y = _objectPoint.y + (i - _occOffsetToCenter);
                _bufPoint.x = _objectPoint.x + (j - _occOffsetToCenter);
                _bufCell = this.getCellData(_bufPoint);
                if(this._shopDesign.quadIsInside(_bufPoint.x,_bufPoint.y) == false && occ_matrix[i][j] != 0){
                    return "Error placing: Trying to put not free occ_point to outside cell ["+_bufPoint.x+","+_bufPoint.y+"] " +
                            "by object on ["+objectData.position.x+","+objectData.position.y+"]";
                }
                if(this._shopDesign.isDoorPoint(_bufPoint) && occ_matrix[i][j] == 1){
                    return "Error placing: Trying to build near the door!!! Cell: ["+_bufPoint.x+","+_bufPoint.y+"] " +
                            "by object on ["+objectData.position.x+","+objectData.position.y+"]";
                }
                if(occ_matrix[i][j] != 0){ //our new cell is not free
                    if(occ_matrix[i][j] == 1){//new cell is owned by objects
                            if(adding){
                                if(_bufCell.occ_var != 0){//place cell is not free => Error
                                    return "Error placing: Trying to own non free cell ["+_bufPoint.x+","+_bufPoint.y+"] " +
                                            "by object on ["+objectData.position.x+","+objectData.position.y+"]";
                                } else {
                                    _bufCell.occ_var = 1;
                                    _bufCell.addObject(objectData);
                                }
                            } else {
                                if(_bufCell.occ_var != 1){//we removing object from no-busy cell => Error
                                    return "Error placing: Trying to own non free cell ["+_bufPoint.x+","+_bufPoint.y+"] " +
                                            "by object on ["+objectData.position.x+","+objectData.position.y+"]";
                                } else{
                                    _bufCell.occ_var = 0;
                                    _bufCell.removeObject(objectData);
                                }
                            }

                    }else if(_bufCell.occ_var <= 0){ //existing cell is not busy (free or reserved)
                        if(adding){
                            _bufCell.occ_var += occ_matrix[i][j];
                            _bufCell.addObject(objectData);
                        } else {
                            _bufCell.occ_var -= occ_matrix[i][j];
                            _bufCell.removeObject(objectData);
                        }
                    } else {
                        return "Error placing: Trying to reserve busy cell ["+_bufPoint.x+","+_bufPoint.y+"] " +
                                "by object on ["+objectData.position.x+","+objectData.position.y+"]";
                    }
                }
            }
        }
        return null;
    }
    public function getCellData(quad:Point):CellData {
        var _result:CellData = this._gameField["x"+quad.x+"y"+quad.y] as CellData;
        if(_result ==  null){
            _result = new CellData(quad);
            this._gameField["x"+quad.x+"y"+quad.y] = _result;
        }
        return _result;
    }


    public function canPlaceWallObject(quad:Point):Boolean {
        if(quad.y == -1){
            if(shopDesign.getDoorAtX(quad.x) != null || shopDesign.getWallCoverAtX(quad.x) != null){
                return false;
            }
        }
        if(quad.x == -1){
            if(shopDesign.getDoorAtY(quad.y) != null || shopDesign.getWallCoverAtY(quad.y) != null){
                return false;
            }
        }
        return true;
    }


    public function set dragSnapPoint(dragSnapPoint:Point):void {
        this._dragSnapPoint = dragSnapPoint;
    }
    public function get dragSnapPoint():Point {
        return _dragSnapPoint;
    }

    public function set dragGameLayer(dragGameLayer:Boolean):void {
        this._dragGameLayer = dragGameLayer;
        sendMessage(ModelToViewMessage.TOGGLE_DRAG_GAME_LAYER,{drag:this._dragGameLayer, point:this._dragSnapPoint, inertion:this._dragInertion});
    }

    public function set dragInertion(dragInertion:Point):void {
        this._dragInertion = dragInertion;
    }


    public function get shoppersPool():Vector.<ShopperData> {
        return _shoppersPool;
    }

    public function get activeShoppersNum():int {
        var _result:int = 0;
        for (var i:int = 0; i < _shoppersPool.length; i++) {
           if(this._shoppersPool[i].isActive){
               _result ++;
           }
        }
        return _result;
    }

    public function getUnusedShopperData():ShopperData {
        for (var i:int = 0; i < this._shoppersPool.length; i++) {
            if(this._shoppersPool[i].isActive == false){
                return this._shoppersPool[i];
                break;
            }
        }
        var newShopperData:ShopperData = new ShopperData();
        this._shoppersPool.push(newShopperData);
        sendMessage(ModelToViewMessage.CREATE_SHOP_OBJECT, newShopperData);
        return newShopperData;
    }


    public function getShelfClientPoint(_bufShelfData:ShopObjectData):Point {
        var _result:Point = _bufShelfData.position.clone();
        switch (_bufShelfData.angle){
            case 45:
                _result.y -= 1;
                break;
            case 135:
                _result.x += 1;
                break;
            case 225:
                _result.y += 1;
                break;
            case 315:
                _result.x -= 1;
                break;
        }

        return _result;
    }

    public function getCashDeskClientPoint(_cashDeskData:ShopObjectData):Point {
        var _result:Point;
        switch (_cashDeskData.angle){
            case 45:
            case 225:
                _result = new Point(_cashDeskData.position.x + 1,_cashDeskData.position.y);
                break;
            case 135:
            case 315:
                _result = new Point(_cashDeskData.position.x,_cashDeskData.position.y + 1);
                break;
        }
        return _result;
    }
    public function selectShelfs():Vector.<ShopObjectData> {
        var i:int = 0;
        var _result:Vector.<ShopObjectData> = new Vector.<ShopObjectData>();
        for (i = 0; i < _staticObjects.length; i++) {
            if(_staticObjects[i].proto.type == Constants.TYPE_SHELF){ //shelf
                _result.push(_staticObjects[i]);
            }
        }
        return _result;
    }

    public function selectCashDesks():Vector.<ShopObjectData> {
        var i:int = 0;
        var _result:Vector.<ShopObjectData> = new Vector.<ShopObjectData>();
        for (i = 0; i < _staticObjects.length; i++) {
            if(_staticObjects[i].proto.type == Constants.TYPE_CASH_DESK){ //cash desk
                _result.push(_staticObjects[i]);
            }
        }
        return _result;
    }

    public function selectNearPoints(targetPoint:Point):Vector.<Point> {
        var _result:Vector.<Point> = new Vector.<Point>();
        _result.push((targetPoint.add(new Point(-1,0))));
        _result.push((targetPoint.add(new Point(0,-1))));
        _result.push((targetPoint.add(new Point(1,0))));
        _result.push((targetPoint.add(new Point(0,1))));


        return _result;
    }

    private var _bufPoint:Point;
    private var _bufCellData:CellData;
    public function filterForWalk(startPoint:Point,points:Vector.<Point>):Vector.<CellData> {
        var _result:Vector.<CellData> = new Vector.<CellData>();
        for (var i:int = 0; i < points.length; i++) {
           this._bufPoint = points[i];
           if(shopDesign.isThroughWall(startPoint, this._bufPoint) == false){
               if(!(this._bufPoint.x == this._shopDesign.sizeX && this._bufPoint.y >= 0) && !(this._bufPoint.y == this._shopDesign.sizeY && this._bufPoint.x >= 0)){
                   this._bufCellData = this.getCellData(this._bufPoint);
                   if(this._bufCellData.occ_var != 1){ //not busy
                       _result.push(this._bufCellData);
                   }
               }
           }
        }

        return _result;
    }

    public function filterForShelfs(points:Vector.<Point>):Vector.<ShopObjectData> {
        var _result:Vector.<ShopObjectData> = new Vector.<ShopObjectData>();
        for (var i:int = 0; i < points.length; i++) {
            this._bufPoint = points[i];
            this._bufCellData = this.getCellData(this._bufPoint);
            if(this._bufCellData.occ_var == 1){
                if(this._bufCellData.objects.length == 1){
                    if(this._bufCellData.objects[0].proto.type == Constants.TYPE_SHELF){
                        _result.push(this._bufCellData.objects[0]);
                    }
                } else {
                    sendMessage(DebugMessage.ERROR_MESSAGE, "RuntymeProxy: CellData on "+this._bufPoint+" must have only one owner " +
                            "(currenrly: "+this._bufCellData.objects.length+"; occ_var = "+this._bufCellData.occ_var+")")
                }
            }
        }

        return _result;
    }

    public function highlightOccMatrix(rotatedOCCMatrix:Array, quad:Point):void {
        var _placingMatrix:Array = getPlacingMatrix(quad,rotatedOCCMatrix);
        this._bufPoint = new Point();
        var _bufLength:int = _placingMatrix.length;
        var _occOffsetToCenter:int = int((_placingMatrix.length-1)/2);
        var _bufColor:uint = 0x00ff00;
        for (var i:int = 0; i < _bufLength; i++) {
            for (var j:int = 0; j < _bufLength; j++) {
                this._bufPoint.setTo(quad.x + (j - _occOffsetToCenter),quad.y + (i - _occOffsetToCenter));
                switch (_placingMatrix[i][j]){
                    case 1://full allow to placing
                        _bufColor = 0x22ff22;
                        break;
                    case 0://allow 1
                        _bufColor = 0x22ffff;
                        break;
                    case -1://deprecate placing
                        _bufColor = 0xff2222;
                        break;
                }
                highlightQuad(this._bufPoint, "placing_"+i+"_"+j, _bufColor, 0.3);
            }
        }
    }
    private var _highlightIDs:Array = [];
    public function highlightQuad(quad:Point, id:String, color:uint = 0x888888, alpha:Number = 0.5):void {
        this._highlightIDs.push(id);
        sendMessage(ModelToViewMessage.HIGHLIGHT_QUAD, {position:quad, id:id, color:color, alpha:alpha});
    }

    public function removeAllHighlights():void {
        for (var i:int = 0; i < this._highlightIDs.length; i++) {
            sendMessage(ModelToViewMessage.REMOVE_HIGHLIGHT_QUAD, {id:this._highlightIDs[i]});
        }
        this._highlightIDs = [];
    }

    public function removeHighLight(id:String):void {
        sendMessage(ModelToViewMessage.REMOVE_HIGHLIGHT_QUAD, {id:id});
    }

    public function get currentTime():uint {
        return this._currentTime;
    }

    public function set currentTime(value:uint):void {
        this._currentTime = value;
        sendMessage(ModelToViewMessage.TIME_UPDATE, {time:this._currentTime})
    }


    public function get warehouseData():WarehouseDataProxy {
        return userProxy.warehouseDataProxy;
    }


    public function setOrderProductAnimationCoords(spriteId:String,start:Point, end:Point):void {
        sendMessage(ModelToViewMessage.NEW_PRODUCT_ORDER_ANIMATION, {sprite_id:spriteId, start:start, end: end});
    }

    public function get bottomPanelLock():Boolean {
        return this._bottomPanelLock;
    }

    public function set bottomPanelLock_zero(value:Boolean):void {
        this._bottomPanelLock = value;
        sendMessage(ModelToViewMessage.LOCK_BOTTOM_PANEL, {value:value})
    }

    public function setMode(mode:String, params:Object):void {
        this._oldMode = this._mode;
        this._mode = mode;
        this._modeParams = params;
        sendMessage(ModelToViewMessage.MODE_CHANGED,{mode:this._mode,mode_old:this._oldMode, mode_params: this._modeParams});
    }

    public function get mode():String {
        return _mode;
    }

    public function get modeParams():Object {
        return _modeParams;
    }

    public function get oldMode():String {
        return _oldMode;
    }


    private var _contextMenuTarget:ShopObjectData;
    public function set contextMenuTarget(contextMenuTarget:ShopObjectData):void {
        this._contextMenuTarget = contextMenuTarget;
        sendMessage(ModelToViewMessage.CONTEXT_MENU_SO_NEW_TARGET, {target:this._contextMenuTarget});
    }
    public function get contextMenuTarget():ShopObjectData {
        return _contextMenuTarget;
    }

    public function get wallObjectContextMenuTarget():Object {
        return this._wallObjectContextMenuTarget;
    }
    ///{full_sprite_name:<String>, quad:<Point>};
    public function set wallObjectContextMenuTarget(value:Object):void {
        this._wallObjectContextMenuTarget = value;
        sendMessage(ModelToViewMessage.CONTEXT_MENU_WO_NEW_TARGET, value);
    }

    public function get dragGameLayer():Boolean {
        return _dragGameLayer;
    }

    public function DEBUG_visualizeOccPoints():void {
        sendMessage(DebugMessage.VISUALIZE_OCC_POINTS, this._gameField);
    }

    public function rotateGameObject(shopObject:ShopObjectData, newAngle:int):Boolean {
        while(newAngle < 0){
            newAngle += 360;
        }
        while(newAngle > 360){
            newAngle -= 360;
        }
        _updateGameFieldOccPoints(shopObject,false);
        if(canBePlaced(shopObject.proto,shopObject.position,newAngle)){
            shopObject.angle = newAngle;
            _updateGameFieldOccPoints(shopObject,true);
            return true;
        }
        _updateGameFieldOccPoints(shopObject,true);
        return false;

    }

}
}
